home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C++
/
Frameworks
/
Sprocket Framework DR2
/
Sprocket Framework
/
AEFutures.cp
next >
Wrap
Text File
|
1996-06-15
|
23KB
|
794 lines
/*
File: AEFutures.cp
Project: Sprocket Framework 1.1 (DR2), released 6/15/96
Contains: Futures package
To Do: ?
Sprocket Major Contributors:
----------------------------
Dave Falkenburg, producer of Sprocket 1.0
Bill Hayden, producer of Sprocket 1.1
Steve Sisak, producer of the upcoming Sprocket 2.0
Pete Alexander Steve Falkenburg Randy Thelen
Eric Berdahl Nitin Ganatra Chris K. Thomas
Marshall Clow Dave Hershey Leonard Rosenthal
Tim Craycroft Dave Mark Dean Yu
David denBoer Gary Powell
Cameron Esfahani Jon Summers Apple Computer, Inc.
Comments, Additions, or Corrections:
------------------------------------
Bill Hayden, Nikol Software <nikol@codewell.com>
*/
#include "AEFutures.h"
#include "Semaphores.h"
#include "SprocketMacros.h"
#ifndef __APPLEEVENTS__
#include <AppleEvents.h>
#endif
//
// Keywords for special handlers not documented
// in Inside Macintosh
//
#define keyAEBlock 'blck'
#define keyAEUnblock 'unbk'
//
// Message ID of the event sent by AEResetTimer
//
#define kAEWaitLonger 'wait'
//
// Random, arbitrarily choosen keyword
// for a parameter that probably doesn't exist.
//
#define keyNonexistantParameter 'nonx'
//
// Structure used just to pass parameters to the "RedispatchEvent" thread
//
struct AsyncPredispatchParameters
{
AppleEvent fAppleEvent;
AppleEvent fReply;
AEEventHandlerUPP fEventHandler;
long fHandlerRefCon;
};
typedef struct AsyncPredispatchParameters AsyncPredispatchParameters;
#if USESROUTINEDESCRIPTORS
//
// The block and unblock special handlers are not
// documented, so they do not have ProcInfo descriptions
// for their callback in the Universal headers. Both
// the block and the unblock routine take a single parameter:
// the AppleEvent that is being blocked on / unblocked.
//
enum
{
uppAEBlockUnblockProcInfo = kPascalStackBased
| RESULT_SIZE(SIZE_CODE(sizeof(OSErr)))
| STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(AEDesc*)))
};
#endif
//
// Private constants:
//
#define kCreateSemaphoreIfNotFound true
#define kDontCreateSemaphoreIfNotFound false
//
// Private functions:
//
static OSErr NewFuturesThread(ThreadEntryProcPtr threadEntry, void *threadParam, long handlerRefCon, ThreadID *threadMade);
static TSemaphore* GetFutureSemaphore(AppleEvent* reply, Boolean createIfNotFound);
static pascal OSErr AEBlock(AppleEvent* reply);
static pascal OSErr AEUnblock(AppleEvent* reply);
static pascal OSErr WaitLongerEvent(AppleEvent* ae, AppleEvent* reply, long refCon);
static pascal OSErr AsyncPreDispatchHandler(AppleEvent* ae, AppleEvent* reply, long refCon);
static void RedispatchEvent(void* threadParam);
static OSErr GetAEMHandlerUPPFromOneTable(AEEventClass theAEEventClass, AEEventID theAEEventID, AEEventHandlerUPP *handler, long *handlerRefcon, Boolean isSysHandler);
static OSErr GetAppleEventHandlerUPP(AppleEvent* ae, AEEventHandlerUPP *handler, long *handlerRefCon);
static long PrivateFuturesThread(long threadParam);
static void FillInDefaultTimeoutValues(long& timeout);
#if USESROUTINEDESCRIPTORS
static RoutineDescriptor gAEBlockRD = BUILD_ROUTINE_DESCRIPTOR(uppAEBlockUnblockProcInfo, AEBlock);
static RoutineDescriptor gAEUnblockRD = BUILD_ROUTINE_DESCRIPTOR(uppAEBlockUnblockProcInfo, AEUnblock);
static RoutineDescriptor gWaitLongerEventRD = BUILD_ROUTINE_DESCRIPTOR(uppAEEventHandlerProcInfo, WaitLongerEvent);
static RoutineDescriptor gAsyncPreDispatchRD = BUILD_ROUTINE_DESCRIPTOR(uppAEEventHandlerProcInfo, AsyncPreDispatchHandler);
#endif // USESROUTINEDESCRIPTORS
//
// Globals:
//
ThreadCreateProcPtr gThreadCreateProc = nil;
/*****************************************************************************/
// Install the block and unblock routines.
//
// If spawnHousekeepingThread is true, then a thread will be forked to idle the
// Futures package. If you pass false for this boolean, your code must call
// IdleFutures Periodicly.
//
// If installAsyncPreDispatchHandler is true, then the futures package will
// install a predispatch handler that automatically forks a new thread every
// time AEProcessAppleEvents is called, so that all of your application's
// event handlers may be processed asynchronously.
OSErr InitFutures(ThreadCreateProcPtr threadCreateProc, long initFuturesFlags)
{
ThreadID futuresHousekeepingThreadID = kNoThreadID;
OSErr err = noErr;
TSemaphore::InitializeGlobals();
gThreadCreateProc = threadCreateProc;
#if USESROUTINEDESCRIPTORS
if(err == noErr)
err = AEInstallSpecialHandler(keyAEBlock, &gAEBlockRD, false);
if(err == noErr)
err = AEInstallSpecialHandler(keyAEUnblock, &gAEUnblockRD, false);
if(err == noErr)
err = AEInstallEventHandler(kCoreEventClass, kAEWaitLonger, &gWaitLongerEventRD, 0, false);
#else
if(err == noErr)
err = AEInstallSpecialHandler(keyAEBlock, (UniversalProcPtr) AEBlock, false);
if(err == noErr)
err = AEInstallSpecialHandler(keyAEUnblock, (UniversalProcPtr) AEUnblock, false);
if(err == noErr)
err = AEInstallEventHandler(kCoreEventClass, kAEWaitLonger, (AEEventHandlerProcPtr) &WaitLongerEvent, 0, false);
#endif
//
// Create the housekeeping thread.
// We could probably use a very small stack for this thread.
//
if((err == noErr) && (initFuturesFlags & kSpawnHousekeepingThread))
err = NewFuturesThread((ThreadEntryProcPtr)PrivateFuturesThread, 0, 0, &futuresHousekeepingThreadID);
//
// If an async pre-dispatch handler was requested, then install it.
//
if((err == noErr) && (initFuturesFlags & kInstallAsyncPreDispatchHandler))
{
#if USESROUTINEDESCRIPTORS
err = AEInstallSpecialHandler(keyPreDispatch, &gAsyncPreDispatchRD, false);
#else
err = AEInstallSpecialHandler(keyPreDispatch, (UniversalProcPtr)AsyncPreDispatchHandler, false);
#endif
}
return err;
}
/*****************************************************************************/
// Block the current thread until the specified message becomes real. This function
// works by accessing a nonexistant parameter of the reply; the AppleEvent manager will
// block any access to the reply, since (obviously) it doesn't know what the parameters
// of the reply will be until the reply actually arrives.
void BlockUntilReal(AppleEvent* reply)
{
long nonExistentParameterStorage;
long actualSize;
DescType typeCode;
// Ask for the non existent parameter; this will cause the AppleEvent manager
// to block on the event
AEGetParamPtr(reply, keyNonexistantParameter, typeLongInteger, &typeCode, (Ptr) &nonExistentParameterStorage, sizeof(long), &actualSize);
}
/*****************************************************************************/
// Return 'true' if the reply can be accessed without blocking, or false if it
// is still a future
Boolean ReplyArrived(AppleEvent* ae)
{
UniversalProcPtr oldBlockRoutine = nil;
long nonExistentParameterStorage;
long actualSize;
DescType typeCode;
Boolean arrived = false;
OSErr err = AEGetSpecialHandler(keyAEBlock, &oldBlockRoutine, false);
if(err == noErr)
err = AERemoveSpecialHandler(keyAEBlock, oldBlockRoutine, false);
//
// Ask for the non existent parameter; this will cause the AppleEvent
// manager to return errAEReplyNotArrived if the reply is still a future;
// some other error (such as paramter not found) will be returned if
// the reply has arrived
//
if(err == noErr)
{
arrived = (AEGetParamPtr(ae, keyNonexistantParameter, typeLongInteger, &typeCode, (Ptr) &nonExistentParameterStorage, sizeof(long), &actualSize) != errAEReplyNotArrived);
err = AEInstallSpecialHandler(keyAEBlock, oldBlockRoutine, false);
}
return arrived;
}
/*****************************************************************************/
// Specify how long the reply should wait in between reset-timer notifications, and
// how much total time the reply should wait before giving up on a server that keeps
// reseting the timer but never delivers results.
void SetReplyTimeoutValue(AppleEvent* reply, long timeoutValue /* = kNeverTimeoutSemaphore */, long maxWaitTime /* = kNeverTimeoutSemaphore */)
{
FillInDefaultTimeoutValues(timeoutValue);
FillInDefaultTimeoutValues(maxWaitTime);
TSemaphore* semaphore = GetFutureSemaphore(reply, kCreateSemaphoreIfNotFound);
if(semaphore)
{
// DebugStr("\pSet reply timout value");
semaphore->SetSemaphoreTimoutValue(timeoutValue);
semaphore->SetSemaphoreMaxWaitTime(maxWaitTime);
}
else
DebugMessage("\pCouldn't make semaphore to set timeout value");
}
/*****************************************************************************/
// Usually, this routine doesn't need to be called, as it is done automatically by
// the private futures thread, installed by InitFutures.
void IdleFutures()
{
TSemaphore::Idle();
}
/*****************************************************************************/
OSErr AskForFuture(AppleEvent* ae, AppleEvent* reply, long timeoutValue /* = kAEDefaultTimeout */, long maxWaitTime /* = 0x7FFFFFFF */, AESendMode sendMode, AESendPriority sendPriority /* = kAENormalPriority */)
{
OSErr err = noErr;
FillInDefaultTimeoutValues(timeoutValue);
long resetFrequency = timeoutValue / 2;
err = AEPutAttributePtr(ae, keyAEResetTimerFrequencyAttr, typeLongInteger, (Ptr)&resetFrequency, sizeof(long));
if(err == noErr)
err = AESend(ae, reply, sendMode | kAEWaitReply, sendPriority, 0, nil, nil);
if(err == errAETimeout)
err = noErr;
if(err == noErr)
SetReplyTimeoutValue(reply, timeoutValue, maxWaitTime);
return err;
}
/*****************************************************************************/
// Determines how frequently a server application is advised to call AEResetTimer
long GetResetTimerFrequency(AppleEvent* ae)
{
DescType typeCode;
long actualSize;
long resetTimerFrequency = 0;
//
// First look at keyAEResetTimerFrequencyAttr; if that attribute
// does not exist, try half of keyAETimeoutAttr
//
if(AEGetAttributePtr(ae, keyAEResetTimerFrequencyAttr, typeLongInteger, &typeCode, (Ptr)&resetTimerFrequency, sizeof(long), &actualSize) != noErr)
{
if(AEGetAttributePtr(ae, keyTimeoutAttr, typeLongInteger, &typeCode, (Ptr)&resetTimerFrequency, sizeof(long), &actualSize) == noErr)
resetTimerFrequency /= 2;
else
resetTimerFrequency = 0;
}
//
// If we couldn't find a frequency, use the default
//
if(resetTimerFrequency == 0)
resetTimerFrequency = kDefaultResetTimerFrequency;
return resetTimerFrequency;
}
/*****************************************************************************/
// Calls AEResetTimer if enough time has elapsed.
OSErr ResetTimerIfNecessary(AppleEvent* reply, unsigned long* lastReset, long resetFrequency)
{
OSErr err = noErr;
unsigned long currentTime = TickCount();
// Set up 'lastReset' the first time in
if(*lastReset == 0)
*lastReset = currentTime;
// We won't be so extreme as to enforce a minimum reset
// frequency, but we don't allow zero!
if(resetFrequency == 0)
resetFrequency = kDefaultResetTimerFrequency;
// TimeExpired is in Semaphores.c; it determines if 'currentTime' has
// gone beyond 'lastReset + resetFrequency', handling the unavoidable
// tickcount wraparound that happens every 771 days.
if (TimeExpired(currentTime, *lastReset, *lastReset + resetFrequency))
{
// DebugStr("\pAbout to call AEResetTimer");
*lastReset = currentTime;
err = AEResetTimer(reply);
}
return err;
}
/*****************************************************************************/
// This routine calls either 'NewThread' or the application-defined thread creation
// procedure.
static OSErr NewFuturesThread(ThreadEntryProcPtr threadEntry, void *threadParam, long handlerRefCon, ThreadID *threadMade)
{
OSErr err = noErr;
Size stackSize = 0;
ThreadOptions options = kCreateIfNeeded | kFPUNotNeeded;
if(gThreadCreateProc != nil)
err = (*gThreadCreateProc)(threadEntry, threadParam, handlerRefCon, threadMade);
else
err = NewThread(kCooperativeThread, threadEntry, threadParam, stackSize, options, nil, threadMade);
return err;
}
/*****************************************************************************/
// This function returns the semaphore that this event is blocked on, or
// nil if the event is not blocked
static TSemaphore* GetFutureSemaphore(AppleEvent* reply, Boolean createIfNotFound)
{
TSemaphore* semaphore = nil;
long semaphoreID = 0;
// Use the reply's return ID as the semaphore ID.
//
// We need to set the semaphore ID when the current thread
// blocks on the future; once the reply arrives, we need to
// pull the semaphore ID out of the reply. Therefore, we'd
// better use an attribute that exists before and after the
// reply arrives.
//
// Another important point: we can access the return ID attribute
// without causing the block routine to be called--very important,
// as we need to get the semaphore out of the message from
// within the block routine!
DescType typeCode;
long actualSize;
if(AEGetAttributePtr(reply, keyReturnIDAttr, typeLongInteger, &typeCode, (Ptr)&semaphoreID, sizeof(long), &actualSize) == noErr)
semaphore = TSemaphore::FindSemaphore(semaphoreID, createIfNotFound, 0);
else
DebugMessage("\pCould not get return attr from reply");
return semaphore;
}
/*****************************************************************************/
// This routine is installed as the special handler "block", which is called by the
// AppleEvent manager whenever a routine that tries to extract information from a
// future (an AppleEvent reply that has not yet arrived) is called.
static pascal OSErr AEBlock(AppleEvent* reply)
{
TSemaphore* semaphore = nil;
OSErr err = noErr;
// DebugStr("\pAEBlock");
// Look up the semaphore attached to this message. This will
// only rarely exist; the only time that it would exist would
// be if multiple threads tried to pull information out of
// the same future. In that case, the first thread would create
// the semaphore, and the second thread would then get and
// block on the same semaphore.
//
semaphore = GetFutureSemaphore(reply, kCreateSemaphoreIfNotFound);
// If we have a semaphore, then block on it.
// 'errAETimeout' is one error that may come out of 'Grab' (if we go to sleep
// for a while, then give up). Note, however, that the AppleEvent manager will always
// return errAEReplyNotArrived if AEBlock returns anything other than noErr.
if(semaphore != nil)
{
err = semaphore->Grab();
semaphore->Release();
}
// If we can't block, then return
// errAEReplyNotArrived right away
else
err = errAEReplyNotArrived;
return err;
}
/*****************************************************************************/
// This function is called by the AppleEvent manager when a future that was blocked
// on becomes a real event (i.e., when the reply actually arrives). When that happens,
// we need to unblock all threads that are blocked on this future.
static pascal OSErr AEUnblock(AppleEvent* reply)
{
TSemaphore* semaphore = nil;
OSErr err = noErr;
// DebugStr("\pAEUnblock");
// The AppleEvent manager may call the unblock routine
// even if the block routine was never called, so there
// may or may not be a semaphore attached to the message
//
semaphore = GetFutureSemaphore(reply, kDontCreateSemaphoreIfNotFound);
if (semaphore != nil)
{
// DebugStr("\pAbout to release all threads");
semaphore->Dispose();
}
return err;
}
/*****************************************************************************/
// Notify the reply that some activity was reported from the server.
static pascal OSErr WaitLongerEvent(AppleEvent* ae, AppleEvent* /*reply*/, long /*refCon*/)
{
// DebugStr("\pWaitLongerEvent");
// Note that the return ID of the wait longer event
// is the same as the return ID of the reply whose
// semaphore we want to look up; the reply to the
// wait longer event is not used for anything, because
// the AppleEvent manager sends the wait longer event
// "no reply".
TSemaphore* semaphore = GetFutureSemaphore(ae, kDontCreateSemaphoreIfNotFound);
if(semaphore)
{
// DebugStr("\pFound semaphore to reset timer on");
semaphore->ResetTimeoutTimer();
}
else
DebugMessage("\pGot wait longer event but did not find semaphore");
return noErr;
}
/*****************************************************************************/
static pascal OSErr AsyncPreDispatchHandler(AppleEvent* ae, AppleEvent* reply, long /*refCon*/)
{
// By default, assume that we are not going to handle this event;
// If we return 'errAEEventNotHandled', the AppleEvent manager will
// continue dispatching this event. We let the AEM handle events
// without any handler, and events that would be handled by a system
// event handler (which we know will never yield, and therefore
// do not need to run in a thread).
OSErr err = errAEEventNotHandled;
AsyncPredispatchParameters** dispatchParams = nil;
AEEventHandlerUPP handler = nil;
long handlerRefCon = 0;
// Look up the event handler and refCon in the AppleEvent tables;
// if we can't find a handler, then we'll let the AppleEvent
// manager do the dispatching.
if(GetAppleEventHandlerUPP(ae, &handler, &handlerRefCon) == noErr)
{
dispatchParams = (AsyncPredispatchParameters**)NewHandle(sizeof(AsyncPredispatchParameters));
if(dispatchParams != nil)
{
// Save the AppleEvent message and reply. We don't need to
// save the event handler's refCon, because the AppleEvent
// manager will look it up again when the event is redispatched.
(*dispatchParams)->fAppleEvent = *ae;
(*dispatchParams)->fReply = *reply;
(*dispatchParams)->fEventHandler = handler;
(*dispatchParams)->fHandlerRefCon = handlerRefCon;
// Make a new thread
ThreadID newThreadID;
if(NewFuturesThread((ThreadEntryProcPtr)RedispatchEvent, (void*)dispatchParams, handlerRefCon, &newThreadID) == noErr)
{
dispatchParams = nil;
// Always suspend the current event. AEResumeTheCurrentEvent
// will be called to redispatch the event after it has been
// forked into a thread.
AESuspendTheCurrentEvent(ae);
// Set 'err' to 'noErr', indicating that we have handled the event.
err = noErr;
}
}
}
// If the 'dispatchParams' were created, but we couldn't
// fork a new thread, then dispose of them here. Otherwise,
// they will be disposed when the event is redispatched.
if(dispatchParams != nil)
DisposeHandle((Handle)dispatchParams);
return err;
}
/*****************************************************************************/
static void RedispatchEvent(void* threadParam)
{
OSErr err = noErr;
// Extract the AppleEvent, Reply, event handler UPP and
// message refCon from the thread parameter, which we
// know is a handle to our predispatch parameters.
AsyncPredispatchParameters** dispatchParams = (AsyncPredispatchParameters**)threadParam;
AppleEvent ae = (*dispatchParams)->fAppleEvent;
AppleEvent reply = (*dispatchParams)->fReply;
AEEventHandlerUPP handler = (*dispatchParams)->fEventHandler;
long handlerRefCon = (*dispatchParams)->fHandlerRefCon;
DisposeHandle((Handle)dispatchParams);
// Call the event handler directly
err = CallAEEventHandlerProc(handler, &ae, &reply, handlerRefCon);
// If the event handler returned an error, we need to do
// exactly what the AppleEvent manager would do in the
// same situation: jam the error into keyErrorNumber
// iff there isn't a keyErrorNumber in the reply already.
// This is necessary because there is no way to pass
// the error code into AEResumeTheCurrentEvent
if(err != noErr)
{
DescType actualType = typeNull;
long actualSize = 0;
long errorResult = 0;
if(AEGetParamPtr(&reply, keyErrorNumber, typeLongInteger, &actualType, &errorResult, sizeof(long), &actualSize) != noErr)
{
errorResult = err;
AEPutParamPtr(&reply, keyErrorNumber, typeLongInteger, &errorResult, sizeof(long));
}
}
// When our handler returns, call AEResumeTheCurrentEvent
// without dispatching, so that the AppleEvent manager
// will send the reply.
AEResumeTheCurrentEvent(&ae, &reply, (AEEventHandlerUPP)kAENoDispatch, 0);
}
/*****************************************************************************/
static OSErr GetAEMHandlerUPPFromOneTable(AEEventClass theAEEventClass, AEEventID theAEEventID, AEEventHandlerUPP *handler, long *handlerRefCon, Boolean isSysHandler)
{
OSErr err = AEGetEventHandler(theAEEventClass, theAEEventID, handler, handlerRefCon, isSysHandler);
if(err != noErr)
err = AEGetEventHandler(typeWildCard, theAEEventID, handler, handlerRefCon, isSysHandler);
if(err != noErr)
err = AEGetEventHandler(theAEEventClass, typeWildCard, handler, handlerRefCon, isSysHandler);
if(err != noErr)
err = AEGetEventHandler(typeWildCard, typeWildCard, handler, handlerRefCon, isSysHandler);
return err;
}
/*****************************************************************************/
static OSErr GetAppleEventHandlerUPP(AppleEvent* ae, AEEventHandlerUPP *handler, long *handlerRefCon)
{
AEEventClass theAEEventClass;
AEEventID theAEEventID;
DescType actualType = typeNull;
long actualSize = 0;
// Look up the event class and the event ID of the AppleEvent
// so that we can look it up in the event handler dispatch table
OSErr err = AEGetAttributePtr(ae, keyEventClassAttr, typeType, &actualType, &theAEEventClass, sizeof(AEEventClass), &actualSize);
if(err == noErr)
err = AEGetAttributePtr(ae, keyEventIDAttr, typeType, &actualType, &theAEEventID, sizeof(AEEventID), &actualSize);
if(err == noErr)
{
err = GetAEMHandlerUPPFromOneTable(theAEEventClass, theAEEventID, handler, handlerRefCon, false);
#if 0
//
// We don't need to search the system event handler table, because we can
// let AEResumeTheCurrentEvent(kAEUseStandardDispatch) do that. We know
// that system event handlers don't yield, so this is safe.
//
if(err != noErr)
err = GetAEMHandlerUPPFromOneTable(theAEEventClass, theAEEventID, handler, handlerRefCon, true);
#endif
}
return err;
}
/*****************************************************************************/
// This thread does periodic tasks for the Futures package; currently, it only checks
// to see if there are any futures that have timed out. Note that TSemaphore::Idle
// calls TickCount(), and performs no action most of the time.
//
// Unfortunately, there's no way for this thread to tell the thread manager that it
// doesn't need to be swapped in for a number of ticks; it has to keep getting scheduled,
// do nothing, get scheduled again, and so on for most of its life.
static long PrivateFuturesThread(long /* threadParam */)
{
for(;;)
{
IdleFutures();
YieldToAnyThread();
}
return 0;
}
/*****************************************************************************/
// This function converts from negative default constants to the actual positive
// number that the constant is translated into.
static void FillInDefaultTimeoutValues(long& timeout)
{
//
// ••• What should the default timeout be?
//
if(timeout == kAEDefaultTimeout)
timeout = 1200;
else if(timeout < 0)
timeout = 0x7FFFFFFF;
}